﻿/*******************************************************************************
** 总结容易出错的问题：
** 1.为线程找连接时，不要用线程id来构造名称去找。因为每个连接任何线程都可以使用。如果这样，可能
** 会出现addDatabase时，导致一个正在被其他线程使用的数据库连接被关闭。从而导致错误发生。
**
*******************************************************************************/

#include "ZDBConnectionPool.h"

#include <Windows.h>
#include <QMapIterator>
#include <QDebug>
#include <QUuid>
#include <QSqlError>

ZFLIB_BEGIN_NAMESPACE

ZDBConnectionPool::ZDBConnectionPool(
        const DBConnectionConfig &config, QObject *parent)
    : QObject(parent), m_mutex(QMutex::Recursive),
      m_db_config(config)
{

}

ZDBConnectionPool::~ZDBConnectionPool()
{
    QMutexLocker locker(&m_mutex);

    // 关闭并删除正在使用的数据库连接
    QMap<qint64, QSqlDatabase>::iterator it = m_busy_connections.begin();
    while (it != m_busy_connections.constEnd())
    {
        // 必须显式关闭连接
        QSqlDatabase &conn = it.value();
        conn.close();

        // 移除此连接，防止连接再次被打开
        QSqlDatabase::removeDatabase(conn.connectionName());
        ++it;
    }
    m_busy_connections.clear();

    // 关闭并删除空闲数据库连接
    int count = m_idle_connection.size();
    for (int i = 0; i < count; i++)
    {
        QSqlDatabase &conn = m_idle_connection[i];

        // 必须显式关闭连接
        conn.close();

        // 移除此连接，防止连接再次被打开
        QSqlDatabase::removeDatabase(conn.connectionName());
    }
    m_idle_connection.clear();
}

QSqlDatabase ZDBConnectionPool::getThreadDBConnection()
{
    QMutexLocker locker(&m_mutex);
    QSqlDatabase connect;

    // 根据线程id对应数据库连接
    qint64 thread_id = ::GetCurrentThreadId();

    // 如果还没有为此线程分配连接，则分配之
    if (!m_busy_connections.contains(thread_id))
    {
        connect = allocConnection();
        m_busy_connections.insert(thread_id, connect);
    }
    // 如果已经分配，则从缓存中取，这发生在一个线程中多处代码需要数据库连接的情况下。
    else
    {
        connect = m_busy_connections.value(thread_id);
    }

    return connect;
}

void ZDBConnectionPool::releaseConnection()
{
    QMutexLocker locker(&m_mutex);

    // 根据线程id对应数据库连接
    qint64 thread_id = ::GetCurrentThreadId();
    QSqlDatabase connect;

    if (m_busy_connections.contains(thread_id))
    {
        connect = m_busy_connections.take(thread_id);

        // 放到空闲队列中
        m_idle_connection.append(connect);
    }
    else
    {
        // 没有和调用者对应的数据库连接，不予响应
    }
}

QSqlDatabase ZDBConnectionPool::createNewConnection()
{
    // 使用随机串作为连接名
    QSqlDatabase connect = QSqlDatabase::addDatabase(
                m_db_config.db_type, QUuid::createUuid().toString());
    connect.setHostName(m_db_config.hostname);
    connect.setUserName(m_db_config.username);
    connect.setPassword(m_db_config.password);
    connect.setPort(m_db_config.port);
    if (!connect.open())
    {
        qWarning() << "Open database error:" << connect.lastError().text();
    }

    return connect;
}

QSqlDatabase ZDBConnectionPool::allocConnection()
{
    QSqlDatabase connect;
    QMutexLocker locker(&m_mutex);

    if (m_idle_connection.isEmpty())
    {
        // 新建
        connect = createNewConnection();
    }
    else
    {
        connect = m_idle_connection.takeFirst();
    }

    return connect;
}

ZFLIB_END_NAMESPACE
